#include <stdio.h>
#include <string.h>

#include <vector>

#include <SDL.h>
#include <SDL_net.h>
#include <cryptlib.h>
#include <sha.h>

#include "Config.h"
#include "EncryptedStream.h"
#include "StringEncoding.h"
#include "FileSystem.h"

const int windowWidth = 640;
const int windowHeight = 360;

SDL_Window *sdlWindow = NULL;
SDL_Renderer *sdlRenderer = NULL;
SDL_Event sdlEvent = {};
bool sdlQuit = false;

int main(int argc, char** argv) {
	Config::loadConfig();

	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) == -1) {
		printf("ERROR: failed to initalize SDL2\n");
		return -1;
	}

	if (SDLNet_Init() == -1) {
		printf("ERROR: failed to initalize SDL2_net\n");
		SDL_Quit();
		return -1;
	}

	if (SDL_CreateWindowAndRenderer(windowWidth, windowHeight, 0, &sdlWindow, &sdlRenderer) == -1) {
		printf("ERROR: failed to create window\n");
		SDLNet_Quit();
		SDL_Quit();
		return -1;
	}

	SDL_SetWindowTitle(sdlWindow, "PotServer");

	TCPsocket servsock = NULL;
	SDLNet_SocketSet servsockset = NULL;

	EncryptedStream stream;
	std::vector<char> tempbuf(1048576);

	IPaddress ipaddr;
	SDLNet_ResolveHost(&ipaddr, NULL, Config::getConfigAsInt("port"));
	servsock = SDLNet_TCP_Open(&ipaddr);
	if (servsock == NULL) {
		printf("ERROR: failed to listen\n");
	} else {
		servsockset = SDLNet_AllocSocketSet(1);
		SDLNet_TCP_AddSocket(servsockset, servsock);
	}

	while (true) {
		while (SDL_PollEvent(&sdlEvent)) {
			switch (sdlEvent.type) {
			case SDL_QUIT:
				sdlQuit = true;
				break;
			case SDL_KEYDOWN:
				if (sdlEvent.key.keysym.mod & KMOD_CTRL) {
					switch (sdlEvent.key.keysym.sym) {
					case SDLK_q:
						sdlQuit = true;
						break;
					}
				}
				break;
			}
		}

		while (servsockset && SDLNet_CheckSockets(servsockset, 0) > 0) {
			if (SDLNet_SocketReady(servsock)) {
				TCPsocket tmpsock = SDLNet_TCP_Accept(servsock);
				if (tmpsock) {
					if (IPaddress *remoteaddr = SDLNet_TCP_GetPeerAddress(tmpsock)) {
						printf("%d.%d.%d.%d connected\n",
							(remoteaddr->host >> 0) & 0xFF,
							(remoteaddr->host >> 8) & 0xFF,
							(remoteaddr->host >> 16) & 0xFF,
							(remoteaddr->host >> 24) & 0xFF);
					}
					stream.create(tmpsock);
				}
			}
		}

		while (stream.check(0)) {
			std::string cmd;
			if (stream.receiveString(cmd, 1)) {
				if (cmd == "mkdir") {
					std::wstring fn;
					if (stream.receiveString(fn, 2)) {
						fn = getCurrentPath() + fn;
						printf("%s %s\n", cmd.c_str(), StringEncoding::fromUTF16(fn, StringEncoding::ANSI).c_str());
						_wmkdir(fn.c_str());
					}
				} else if (cmd == "send") {
					std::wstring fn;
					if (stream.receiveString(fn, 2)) {
						fn = getCurrentPath() + fn;
						printf("%s %s\n", cmd.c_str(), StringEncoding::fromUTF16(fn, StringEncoding::ANSI).c_str());

						unsigned long long fileSize = 0;
						if (stream.receive(&fileSize, sizeof(fileSize), true) == sizeof(fileSize)) {
							FILE *f = _wfopen(fn.c_str(), L"wb");
							if (f == NULL) {
								printf("[ERROR] Failed to open file\n");
							}

							CryptoPP::SHA256 hash;

							while (fileSize > 0) {
								size_t i = (size_t)std::min<unsigned long long>(fileSize, tempbuf.size());
								size_t j = stream.receive(tempbuf.data(), i, false);
								if (j <= 0) break;

								fileSize -= j;

								hash.Update((const CryptoPP::byte*)tempbuf.data(), j);
								if (f) fwrite(tempbuf.data(), 1, j, f);
							}

							fclose(f);

							hash.Final((CryptoPP::byte*)tempbuf.data());

							if (stream.receive(tempbuf.data() + hash.DigestSize(), hash.DigestSize(), true) == hash.DigestSize()) {
								if (memcmp(tempbuf.data(), tempbuf.data() + hash.DigestSize(), hash.DigestSize()) != 0) {
									printf("[ERROR] Hash mismatch, GFWGFW\n");
									stream.destroy();
								}
							}
						}
					}
				} else {
					printf("[ERROR] Unknown command '%s', GFWGFW\n", cmd.c_str());
					stream.destroy();
				}
			}
		}

		if (sdlQuit) break;

		SDL_SetRenderDrawColor(sdlRenderer, 0, 0, 0, 255);
		SDL_RenderClear(sdlRenderer);

		SDL_RenderPresent(sdlRenderer);
		SDL_Delay(30);
	}

	if (servsock) {
		SDLNet_TCP_DelSocket(servsockset, servsock);
		SDLNet_TCP_Close(servsock);
		SDLNet_FreeSocketSet(servsockset);
		servsock = NULL;
		servsockset = NULL;
	}
	stream.destroy();
	SDLNet_Quit();

	SDL_DestroyRenderer(sdlRenderer);
	SDL_DestroyWindow(sdlWindow);
	SDL_Quit();

	return 0;
}
